/*
 * HTTP Server Functions Implementation
 * Author: Qoder
 * Description: Implementation of HTTP server functionality using standard socket programming
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
#include <ctype.h>

#include "server_functions.h"
#include "cgi_functions.h"

// Helper function for case-insensitive string search
char *strcasestr_custom(const char *haystack, const char *needle) {
    if (!haystack || !needle) return NULL;
    
    size_t needle_len = strlen(needle);
    if (needle_len == 0) return (char *)haystack;
    
    for (; *haystack; haystack++) {
        if (strncasecmp(haystack, needle, needle_len) == 0) {
            return (char *)haystack;
        }
    }
    return NULL;
}

void *handle_client(void *arg) {
    client_info_t *client_info = (client_info_t*)arg;
    int client_fd = client_info->socket_fd;
    char buffer[BUFFER_SIZE];
    int bytes_received;
    http_request_t request;
    
    // Receive HTTP request
    bytes_received = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
    if (bytes_received <= 0) {
        close(client_fd);
        free(client_info);
        return NULL;
    }
    
    buffer[bytes_received] = '\0';
    
    // Parse HTTP request
    if (parse_http_request(buffer, &request) != 0) {
        send_error_response(client_fd, HTTP_400_BAD_REQUEST, "Invalid HTTP request");
        close(client_fd);
        free(client_info);
        return NULL;
    }
    
    // Log request
    printf("[%s] %s %s\n", 
           inet_ntoa(client_info->client_addr.sin_addr),
           get_method_string(request.method), 
           request.path);
    
    // Route request
    if (request.method == HTTP_GET) {
        if (strcmp(request.path, "/ping") == 0) {
            handle_ping(client_fd, &request);
        } else if (strncmp(request.path, "/download/", 10) == 0) {
            handle_download(client_fd, &request);
        } else if (strncmp(request.path, "/cgi/", 5) == 0) {
            handle_cgi_request(client_fd, &request);
        } else {
            handle_404(client_fd, &request);
        }
    } else if (request.method == HTTP_POST) {
        if (strncmp(request.path, "/cgi/", 5) == 0) {
            handle_cgi_request(client_fd, &request);
        } else {
            send_error_response(client_fd, HTTP_405_METHOD_NOT_ALLOWED, "Method not allowed");
        }
    } else {
        send_error_response(client_fd, HTTP_405_METHOD_NOT_ALLOWED, "Method not allowed");
    }
    
    // Cleanup
    free_http_request(&request);
    close(client_fd);
    free(client_info);
    return NULL;
}

int parse_http_request(const char *raw_request, http_request_t *request) {
    memset(request, 0, sizeof(http_request_t));
    
    char *request_copy = strdup(raw_request);
    if (!request_copy) return -1;
    
    // Parse request line
    char *line = strtok(request_copy, "\n");
    if (!line) {
        free(request_copy);
        return -1;
    }
    
    // Remove carriage return
    char *cr = strchr(line, '\r');
    if (cr) *cr = '\0';
    
    // Parse method, path, and version
    char *method_str = strtok(line, " ");
    char *path_str = strtok(NULL, " ");
    char *version_str = strtok(NULL, " ");
    
    if (!method_str || !path_str || !version_str) {
        free(request_copy);
        return -1;
    }
    
    request->method = get_http_method(method_str);
    
    // Parse path and query string
    char *query = strchr(path_str, '?');
    if (query) {
        *query = '\0';
        query++;
        request->query_string = strdup(query);
    }
    request->path = strdup(path_str);
    
    // Parse headers and body
    char *headers_start = strchr(raw_request, '\n');
    if (headers_start) {
        headers_start++;
        char *body_start = strstr(headers_start, "\r\n\r\n");
        if (body_start) {
            *body_start = '\0';
            body_start += 4;
            request->headers = strdup(headers_start);
            
            // Check for Content-Length header
            char *content_length_line = strcasestr_custom(request->headers, "content-length:");
            if (content_length_line) {
                content_length_line += 15; // Skip "content-length:"
                while (*content_length_line == ' ') content_length_line++;
                request->content_length = atoi(content_length_line);
                
                if (request->content_length > 0) {
                    request->body = strndup(body_start, request->content_length);
                }
            }
        } else {
            request->headers = strdup(headers_start);
        }
    }
    
    free(request_copy);
    return 0;
}

void free_http_request(http_request_t *request) {
    if (request->path) free(request->path);
    if (request->query_string) free(request->query_string);
    if (request->headers) free(request->headers);
    if (request->body) free(request->body);
    memset(request, 0, sizeof(http_request_t));
}

http_method_t get_http_method(const char *method_str) {
    if (strcasecmp(method_str, "GET") == 0) return HTTP_GET;
    if (strcasecmp(method_str, "POST") == 0) return HTTP_POST;
    if (strcasecmp(method_str, "PUT") == 0) return HTTP_PUT;
    if (strcasecmp(method_str, "DELETE") == 0) return HTTP_DELETE;
    return HTTP_UNKNOWN;
}

const char *get_method_string(http_method_t method) {
    switch (method) {
        case HTTP_GET: return "GET";
        case HTTP_POST: return "POST";
        case HTTP_PUT: return "PUT";
        case HTTP_DELETE: return "DELETE";
        default: return "UNKNOWN";
    }
}

void send_http_response(int client_fd, const char *status, const char *content_type, 
                       const char *body, int body_length) {
    char response_header[BUFFER_SIZE];
    time_t now;
    struct tm *gmt;
    char date_buffer[128];
    
    time(&now);
    gmt = gmtime(&now);
    strftime(date_buffer, sizeof(date_buffer), "%a, %d %b %Y %H:%M:%S GMT", gmt);
    
    int header_length = snprintf(response_header, sizeof(response_header),
        "%s %s\r\n"
        "Server: %s\r\n"
        "Date: %s\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %d\r\n"
        "Connection: close\r\n"
        "\r\n",
        HTTP_VERSION, status, SERVER_NAME, date_buffer, content_type, body_length);
    
    send(client_fd, response_header, header_length, 0);
    if (body && body_length > 0) {
        send(client_fd, body, body_length, 0);
    }
}

void send_json_response(int client_fd, const char *status, const char *json_data) {
    send_http_response(client_fd, status, "application/json", json_data, strlen(json_data));
}

void send_file_response(int client_fd, const char *file_path) {
    FILE *file = fopen(file_path, "rb");
    if (!file) {
        send_error_response(client_fd, HTTP_404_NOT_FOUND, "File not found");
        return;
    }
    
    // Get file size
    fseek(file, 0, SEEK_END);
    long file_size = ftell(file);
    fseek(file, 0, SEEK_SET);
    
    // Get file extension for MIME type
    const char *extension = strrchr(file_path, '.');
    const char *mime_type = get_mime_type(extension ? extension + 1 : "");
    
    // Send response header
    char response_header[BUFFER_SIZE];
    time_t now;
    struct tm *gmt;
    char date_buffer[128];
    
    time(&now);
    gmt = gmtime(&now);
    strftime(date_buffer, sizeof(date_buffer), "%a, %d %b %Y %H:%M:%S GMT", gmt);
    
    int header_length = snprintf(response_header, sizeof(response_header),
        "%s %s\r\n"
        "Server: %s\r\n"
        "Date: %s\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %ld\r\n"
        "Content-Disposition: attachment; filename=\"%s\"\r\n"
        "Connection: close\r\n"
        "\r\n",
        HTTP_VERSION, HTTP_200_OK, SERVER_NAME, date_buffer, mime_type, file_size,
        strrchr(file_path, '/') ? strrchr(file_path, '/') + 1 : file_path);
    
    send(client_fd, response_header, header_length, 0);
    
    // Send file content
    char file_buffer[BUFFER_SIZE];
    size_t bytes_read;
    while ((bytes_read = fread(file_buffer, 1, sizeof(file_buffer), file)) > 0) {
        send(client_fd, file_buffer, bytes_read, 0);
    }
    
    fclose(file);
}

void send_error_response(int client_fd, const char *status, const char *message) {
    char json_response[512];
    snprintf(json_response, sizeof(json_response),
        "{\n"
        "  \"error\": true,\n"
        "  \"message\": \"%s\",\n"
        "  \"timestamp\": %ld\n"
        "}", message, time(NULL));
    
    send_json_response(client_fd, status, json_response);
}

void handle_ping(int client_fd, http_request_t *request) {
    char json_response[256];
    snprintf(json_response, sizeof(json_response),
        "{\n"
        "  \"message\": \"pong\",\n"
        "  \"timestamp\": %ld,\n"
        "  \"server\": \"%s\"\n"
        "}", time(NULL), SERVER_NAME);
    
    send_json_response(client_fd, HTTP_200_OK, json_response);
}

void handle_download(int client_fd, http_request_t *request) {
    // Extract filename from path (/download/filename)
    const char *filename = request->path + 10; // Skip "/download/"
    
    if (strlen(filename) == 0) {
        send_error_response(client_fd, HTTP_400_BAD_REQUEST, "No filename specified");
        return;
    }
    
    // Decode URL-encoded filename
    char *decoded_filename = url_decode(filename);
    if (!decoded_filename) {
        send_error_response(client_fd, HTTP_500_INTERNAL_ERROR, "Failed to decode filename");
        return;
    }
    
    // Security check: prevent directory traversal
    if (strstr(decoded_filename, "..") || decoded_filename[0] == '/') {
        send_error_response(client_fd, HTTP_400_BAD_REQUEST, "Invalid filename");
        free(decoded_filename);
        return;
    }
    
    // Check if file exists
    if (!file_exists(decoded_filename)) {
        send_error_response(client_fd, HTTP_404_NOT_FOUND, "File not found");
        free(decoded_filename);
        return;
    }
    
    send_file_response(client_fd, decoded_filename);
    free(decoded_filename);
}

void handle_cgi_request(int client_fd, http_request_t *request) {
    // Delegate to CGI functions
    process_cgi_request(client_fd, request);
}

void handle_404(int client_fd, http_request_t *request) {
    send_error_response(client_fd, HTTP_404_NOT_FOUND, "Resource not found");
}

char *get_mime_type(const char *file_extension) {
    if (!file_extension) return "application/octet-stream";
    
    if (strcasecmp(file_extension, "html") == 0 || strcasecmp(file_extension, "htm") == 0)
        return "text/html";
    if (strcasecmp(file_extension, "css") == 0) return "text/css";
    if (strcasecmp(file_extension, "js") == 0) return "application/javascript";
    if (strcasecmp(file_extension, "json") == 0) return "application/json";
    if (strcasecmp(file_extension, "txt") == 0) return "text/plain";
    if (strcasecmp(file_extension, "jpg") == 0 || strcasecmp(file_extension, "jpeg") == 0)
        return "image/jpeg";
    if (strcasecmp(file_extension, "png") == 0) return "image/png";
    if (strcasecmp(file_extension, "gif") == 0) return "image/gif";
    if (strcasecmp(file_extension, "pdf") == 0) return "application/pdf";
    if (strcasecmp(file_extension, "zip") == 0) return "application/zip";
    
    return "application/octet-stream";
}

char *url_decode(const char *encoded) {
    if (!encoded) return NULL;
    
    size_t len = strlen(encoded);
    char *decoded = malloc(len + 1);
    if (!decoded) return NULL;
    
    size_t i, j;
    for (i = 0, j = 0; i < len; i++, j++) {
        if (encoded[i] == '%' && i + 2 < len) {
            int hex_value;
            if (sscanf(encoded + i + 1, "%2x", &hex_value) == 1) {
                decoded[j] = (char)hex_value;
                i += 2;
            } else {
                decoded[j] = encoded[i];
            }
        } else if (encoded[i] == '+') {
            decoded[j] = ' ';
        } else {
            decoded[j] = encoded[i];
        }
    }
    decoded[j] = '\0';
    
    return decoded;
}

int file_exists(const char *file_path) {
    struct stat buffer;
    return (stat(file_path, &buffer) == 0);
}

long get_file_size(const char *file_path) {
    struct stat buffer;
    if (stat(file_path, &buffer) != 0) return -1;
    return buffer.st_size;
}